﻿using System;
using System.Collections;
#if WINDOWS_PHONE
using StyleMVVM.Data.Collections;
#else
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data.Collections;
using StyleMVVM.DependencyInjection;
using StyleMVVM.Logging;

#if !DOT_NET
using Windows.Foundation.Collections;

#endif

namespace StyleMVVM.Data.Activation
{
	public sealed class ActivationService : IActivationService
	{
		private static readonly string supplementalString = typeof(ActivationService).FullName;
		private static readonly object lockObject = new object();
		private static MethodInfo cloneClosedIObservableMapInfo;
		private static MethodInfo cloneClosedIDictionaryInfo;
		private static WeakReference activationService;
		private IDependencyInjectionContainer container;
		private ConcurrentDictionary<Type, IActivationStrategy> injectionStrategies;
		private ConcurrentDictionary<Type, IActivationStrategy> activationStrategies;
		private ConcurrentDictionary<Type, IDeepCloneStrategy> deepCloneStrategies;

		public static void RegisterExport(IDependencyInjectionContainer container)
		{
			cloneClosedIObservableMapInfo =
				typeof(ActivationService).GetTypeInfo().GetDeclaredMethod("cloneClosedIObservableMap");

			cloneClosedIDictionaryInfo =
				typeof(ActivationService).GetTypeInfo().GetDeclaredMethod("cloneClosedIDictionary");

			container.Register(typeof(ActivationService)).
						 ImportMethod("ImportContainer").
						 As(typeof(IActivationService)).
						 AndSharedPermenantly();
		}

		//public ActivationService()
		//{
		//	Initialize();	
		//}

		public ActivationService(IDependencyInjectionContainer container)
		{
			this.container = container;

			Initialize();
		}

		public void ImportContainer(IDependencyInjectionContainer container)
		{
			this.container = container;
		}

		public static IActivationService Instance
		{
			get
			{
				IActivationService returnValue = null;

				if (activationService != null)
				{
					returnValue = activationService.Target as IActivationService;
				}

				if (returnValue == null)
				{
					if (Bootstrapper.HasInstance)
					{
						lock (lockObject)
						{
							if (activationService == null || activationService.Target == null)
							{
								activationService = new WeakReference(
									Bootstrapper.Instance.Container.LocateByType(typeof(IActivationService)));
							}

							returnValue = activationService.Target as IActivationService;
						}
					}
				}

				return returnValue;
			}
		}

		public ITypeAugmenter TypeAugmenter { get; set; }

		public void Inject(object injectObject)
		{
			Inject(container.CreateInjectionContext(), injectObject);
		}

		public void Inject(IInjectionContext injectionContext, object injectObject)
		{
			Type injectType = injectObject.GetType();
			IActivationStrategy injectStrategy;

			if (!injectionStrategies.TryGetValue(injectType, out injectStrategy))
			{
				// we are going to pass the injection object through the constructor params as the first arguement
				ExportFunction exportFunction = (injectionContainer, context, parameters) => parameters[0];

				ActivationTypeInfo typeInfo = new ActivationTypeInfo
															{
																ExportFunction = exportFunction,
																ActivationType = injectType,
																DiscoverAttributes = true
															};

				CompiledActivationStrategy newStrategy = new CompiledActivationStrategy(typeInfo, TypeAugmenter);

				newStrategy.Container = container;
				newStrategy.ActivationService = this;

				newStrategy.Initialize();

				injectionStrategies.AddOrUpdate(injectType, x => newStrategy, (x, y) => newStrategy);

				injectStrategy = newStrategy;
			}

			injectStrategy.Activate(injectionContext, new[] { injectObject });
		}

		public object CreateByType(IInjectionContext injectionContext, Type createType, params object[] constructorParams)
		{
			IActivationStrategy strategy = LocateActivationStrategy(createType, true, false);

			return strategy.Activate(injectionContext, constructorParams);
		}

		public object DeepClone(object cloneValue)
		{
			if ((object)cloneValue != null &&
				 !cloneValue.GetType().GetTypeInfo().IsValueType)
			{
				return DeepCloneIntoInstance(cloneValue, null);
			}

			return null;
		}

		public object DeepCloneIntoInstance(object cloneValue, object tInstance)
		{
			IInjectionContext injectionContext = container.CreateInjectionContext();

			if ((object)cloneValue != null)
			{
				Type valueType = cloneValue.GetType();

				if (!valueType.GetTypeInfo().IsValueType)
				{
					IDeepCloneStrategy cloneStrategy = null;

					if (tInstance == null)
					{
						if (!cloneValue.GetType().IsArray)
						{
							tInstance = CreateByType(injectionContext, cloneValue.GetType());
						}
						else
						{
							tInstance = CreateByType(injectionContext, cloneValue.GetType(), ((Array)cloneValue).Length);
						}
					}

					if (!deepCloneStrategies.TryGetValue(valueType, out cloneStrategy))
					{
						IList valueList = cloneValue as IList;

						if (valueList != null)
						{
							cloneList(cloneValue as IList, tInstance as IList);

							return tInstance;
						}

						IDictionary dictionary = cloneValue as IDictionary;

						if (dictionary != null)
						{
							cloneDictionary(cloneValue as IDictionary, tInstance as IDictionary);

							return tInstance;
						}

						foreach (Type implementedInterface in
							valueType.GetTypeInfo().ImplementedInterfaces)
						{
							if (implementedInterface.IsConstructedGenericType)
							{
								Type genericType =
									implementedInterface.GetTypeInfo().GetGenericTypeDefinition();

#if !DOT_NET
								if (genericType == typeof(IObservableMap<,>))
								{
									cloneMap(cloneValue, tInstance);
									return tInstance;
								}
#else
								if (genericType == typeof(IDictionary<,>))
								{
									cloneDictionary(cloneValue, tInstance);
									return tInstance;
								}
#endif
							}
						}

						cloneStrategy = new CompiledDeepCloneStrategy(valueType);

						cloneStrategy.ActivationService = this;
						cloneStrategy.Initialize();

						deepCloneStrategies.TryAdd(valueType, cloneStrategy);
					}

					if (cloneStrategy != null)
					{
						return cloneStrategy.DeepClone(cloneValue, tInstance);
					}
				}
				else
				{
					return cloneValue;
				}
			}

			return tInstance;
		}

		public IActivationStrategy CreateActivationStrategy(IActivationTypeInfo typeInfo)
		{
			IActivationStrategy activationStrategy = null;

			try
			{
				activationStrategy =
					new CompiledActivationStrategy(typeInfo, TypeAugmenter)
						{
							Container = container,
							ActivationService = this
						};

				activationStrategy.Initialize();
			}
			catch (Exception exp)
			{
				Logger.Error("An exception was thrown while creating activation strateby for: " + typeInfo.ActivationType.FullName,
								 supplementalString,
								 exp);
			}

			return activationStrategy;
		}

		public void InitializeActivationType(IActivationTypeInfo typeInfo)
		{
			try
			{
				IActivationStrategy activationStrategy = null;

				if (!activationStrategies.TryGetValue(typeInfo.ActivationType, out activationStrategy))
				{
					activationStrategy =
						new CompiledActivationStrategy(typeInfo, TypeAugmenter)
							{
								Container = container,
								ActivationService = this
							};

					activationStrategy.Initialize();

					activationStrategies.AddOrUpdate(typeInfo.ActivationType,
																(x) => activationStrategy,
																(x, y) => activationStrategy);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("An exception was thrown while creating activation strateby for: " + typeInfo.ActivationType.FullName,
								 supplementalString,
								 exp);
			}
		}

		private void Initialize()
		{
			activationStrategies =
				new ConcurrentDictionary<Type, IActivationStrategy>();

			deepCloneStrategies = new ConcurrentDictionary<Type, IDeepCloneStrategy>();

			injectionStrategies = new ConcurrentDictionary<Type, IActivationStrategy>();
		}

		private IActivationStrategy LocateActivationStrategy(Type tType, bool registerHandlers, bool suspendable)
		{
			IActivationStrategy activationStrategy = null;

			try
			{
				if (!activationStrategies.TryGetValue(tType, out activationStrategy))
				{
					activationStrategy =
						new CompiledActivationStrategy(
							new ActivationTypeInfo
								{
									ActivationType = tType,
									DiscoverAttributes = true,
								}, TypeAugmenter);

					activationStrategy.Container = container;
					activationStrategy.ActivationService = this;
					activationStrategy.RegisterHandlers = registerHandlers;
					activationStrategy.Suspendable = suspendable;

					activationStrategy.Initialize();

					activationStrategies.AddOrUpdate(tType, (x) => activationStrategy, (x, y) => activationStrategy);
				}
			}
			catch (Exception exp)
			{
				Logger.Error("An exception was thrown while creating activation strateby for: " + tType.FullName,
								 supplementalString,
								 exp);
			}

			return activationStrategy;
		}

		private void cloneDictionary(IDictionary tValue, IDictionary tClone)
		{
			foreach (object key in tValue.Keys)
			{
				tClone.Add(key, DeepClone(tValue[key]));
			}
		}

		private void cloneList(IList valueList, IList cloneList)
		{
			if (cloneList.GetType().IsArray)
			{
				for (int i = 0; i < valueList.Count; i++)
				{
					cloneList[i] = DeepClone(valueList[i]);
				}
			}
			else
			{
				for (int i = 0; i < valueList.Count; i++)
				{
					cloneList.Add(DeepClone(valueList[i]));
				}
			}
		}

		private void cloneDictionary(object value, object clone)
		{
			MethodInfo closedMethod =
				cloneClosedIDictionaryInfo.MakeGenericMethod(
					value.GetType().GetTypeInfo().GenericTypeParameters);

			closedMethod.Invoke(this, new[] { value, clone });
		}

		private void cloneClosedIDictionary<K, V>(
			IDictionary<K, V> tValue, IDictionary<K, V> tClone)
		{
			foreach (KeyValuePair<K, V> keyValuePair in tValue)
			{
				tClone.Add(new KeyValuePair<K, V>(
								  keyValuePair.Key, (V)DeepClone(keyValuePair.Value)));
			}
		}

		private void cloneMap(object value, object clone)
		{
			MethodInfo closedMethod =
				cloneClosedIObservableMapInfo.MakeGenericMethod(
					value.GetType().GetTypeInfo().GenericTypeParameters);

			closedMethod.Invoke(this, new[] { value, clone });
		}

		private void cloneClosedIObservableMap<K, V>(
			IObservableMap<K, V> tValue, IObservableMap<K, V> tClone)
		{
			foreach (KeyValuePair<K, V> keyValuePair in tValue)
			{
				tClone.Add(new KeyValuePair<K, V>(
								  keyValuePair.Key, (V)DeepClone(keyValuePair.Value)));
			}
		}

		private void processPipeLineHandlers()
		{
		}
	}
}